package org.cafeanimal.sqlite;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

import com.google.common.base.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Used to build {@linkplain TableDef} objects.
 *
 * @author michael
 */
public class TableDefBuilder {
    private static final Logger LOGGER = LoggerFactory.getLogger(TableDefBuilder.class);
    
    private TableDef.Params params;
    private Set<Set<String>> uniqueSets;

    public TableDefBuilder() {
        doReset();
    }
    
    public TableDefBuilder name(String name) {
        params.name = name;
        return this;
    }

    /**
     * Adds a primary key field.
     * 
     * @param name field name
     * @param type field type
     * @return the builder
     */
    public TableDefBuilder primaryKeyField(String name, FieldType type) {
        if (isUnusedName(name)) {
            params.fields.add(new Field(name, type, true));
        }
        return this;
    }

    /**
     * Adds an integer field.
     * 
     * @param name field name
     * @return the builder
     */
    public TableDefBuilder intField(String name) {
        return field(name, FieldType.INTEGER);
    }
    
    /**
     * Adds a real (double precision) field.
     * 
     * @param name field name
     * @return the builder
     */
    public TableDefBuilder realField(String name) {
        return field(name, FieldType.REAL);
    }
    
    /**
     * Adds a text (string) field.
     * 
     * @param name field name
     * @return the builder
     */
    public TableDefBuilder textField(String name) {
        return field(name, FieldType.TEXT);
    }
    
    /**
     * Adds a boolean field (SQLite stores this as integer with values 0 or 1).
     * 
     * @param name field name
     * @return the builder
     */
    public TableDefBuilder boolField(String name) {
        return field(name, FieldType.BOOLEAN);
    }
    
    /**
     * Adds a field.
     * 
     * @param name field name
     * @param type field type
     * @return the builder
     */
    public TableDefBuilder field(String name, FieldType type) {
        if (isUnusedName(name)) {
            params.fields.add(new Field(name, type, false));
        }
        return this;
    }

    /**
     * Adds a UNIQUE table constraint for the fields named. 
     * 
     * @param fldNames
     * @return 
     */
    public TableDefBuilder unique(String ...fldNames) {
        Set<String> s = new LinkedHashSet<String>(Arrays.asList(fldNames));
        boolean ok = s.size() == fldNames.length;
        
        if (!ok) {
            LOGGER.error("Duplicate field names are not valid "
                    + "({} field names supplied but only {} are unique).",
                    fldNames.length, s.size());
            
            return this;
            
        } else {
            ok = uniqueSets.add(s);
            if (!ok) {
                LOGGER.error("In unique constraint, duplicate set of field names {}", s);
            }
        }
        
        return this;
    }

    /**
     * Builds the table definition.
     * 
     * @return an {@linkplain Optional} object which will have the table definition
     *     as its value if building was successful
     */
    public Optional<TableDef> build() {
        if (processParams()) {
            return Optional.of(new TableDef(params));
        } else {
            return Optional.absent();
        }
    }
    
    /**
     * Clears the contents of this builder.
     */
    public void reset() {
        doReset();
    }
    
    private void doReset() {
        params = new TableDef.Params();
        uniqueSets = new LinkedHashSet<Set<String>>();
    }

    private boolean processParams() {
        if (params.name == null || params.name.trim().length() == 0) {
            return false;
        }
        if (params.fields.isEmpty()) {
            return false;
        }
        
        for (Set<String> u : uniqueSets) {
            Set<Field> sf = new LinkedHashSet<Field>();
            for (String name : u) {
                Optional<Field> op = getField(name);
                if (!op.isPresent()) {
                    LOGGER.error("Unknown field {} included in unique constraint", name);
                    return false;
                }
                
                sf.add(op.get());
            }
            params.constraints.add(new UniqueConstraint(sf));
        }
        
        return true;
    }
    
    private Optional<Field> getField(String fldName) {
        for (Field f : params.fields) {
            if (f.getName().equalsIgnoreCase(fldName)) {
                return Optional.of(f);
            }
        }
        
        return Optional.absent();
    }

    private boolean isUnusedName(String name) {
        if (getField(name).isPresent()) {
            LOGGER.error("Duplicate field name: {}", name);
            return false;
        }
        
        return true;
    }

}
